<?php

namespace plugin\cms\app\bll;

use app\bll\User;
use app\bll\UserGrade;
use core\attribute\Template;
use core\lib\schema\Table;
use Exception;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use plugin\cms\app\enum\ContentStatus;
use support\Db;
use Throwable;
use Webman\Event\Event;

/**
 * 内容
 */
class Content
{
    /**
     * 查询
     *
     * @param integer $id
     * @param integer $cid
     * @param integer $siteid
     * @return array
     */
    #[Template]
    public function find(int $id, int $cid, ?int $siteid = null): array
    {
        $bllcategory = new Category();
        $cateinfo    = $bllcategory->find($cid, $siteid);

        $bllModel  = new Model();
        $modelinfo = $bllModel->find($cateinfo['mid'], $siteid);

        $info =  Db::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('id', '=', $id)
            ->orderBy('id', 'desc')
            ->first();

        if ($info === null) {
            return [];
        } else {
            $info = get_object_vars($info);

            if ($info['interacttype'] == 1) {
                $info['comment'] = $cateinfo['comment'];
                $info['praise'] = $cateinfo['praise'];
                $info['collect'] = $cateinfo['collect'];
            }

            return $info;
        }
    }

    /**
     * 添加内容
     *
     * @param array $data
     * @param integer $siteid
     * @return integer
     */
    public function add(array $data, int $siteid): int
    {

        //启用钩子
        Event::dispatch('framecoder.hook', [
            'ident' => 'cms_content_add_before', //钩子标识
            'data'  => $data,
        ]);


        $bllCategory = new Category();
        $cateinfo    = $bllCategory->find($data['cid'], $siteid);
        $bllModel    = new Model();
        $modelinfo = $bllModel->find($cateinfo['mid'], $siteid);

        $bllModelField = new ModelField();
        $fields = $bllModelField->select($modelinfo['id']);
        $data = $this->formatFieldData($fields, $data);

        Db::beginTransaction();
        try {

            $tags = isset($data['tag']) ? $data['tag'] : [];
            unset($data['tag']);

            $data = $this->filterNoExitField($modelinfo['tablename'], $data);
            $data['siteid'] = $siteid;
            $this->verifyField($fields, $data);

            $contentid = Db::table($modelinfo['tablename'])
                ->insertGetId($data);

            //插入到标签内容表
            $tagcontent = [];
            foreach ($tags as $tag) {
                $tagcontent[] = [
                    'cid'       => $data['cid'],
                    'modelid'   => $cateinfo['mid'],
                    'tagid'     => $tag,
                    'contentid' => $contentid,
                    'siteid'    => $siteid,
                ];
            }
            if ($tagcontent) {
                Db::table('cms_tag_content')
                    ->insert($tagcontent);
            }

            $bllCategory->computeContentCountAtCid($data['cid'], $siteid);

            //启用钩子
            Event::dispatch('framecoder.hook', [
                'ident' => 'cms_content_add', //钩子标识
                'data'  => $data
            ]);


            Db::commit();
        } catch (Throwable $ex) {
            Db::rollBack();
            throw $ex;
        }


        return $contentid;
    }


    /**
     * 编辑
     *
     * @param integer $id
     * @param array $data
     * @param integer $siteid
     * @return void
     */
    public function edit(int $id, array $data, int $siteid)
    {
        $bllCategory = new Category();
        $cateinfo    = $bllCategory->find($data['cid'], $siteid);
        $bllModel    = new Model();
        $modelinfo = $bllModel->find($cateinfo['mid'], $siteid);

        $bllModelField = new ModelField();
        $fields = $bllModelField->select($modelinfo['id']);
        $data = $this->formatFieldData($fields, $data, 'edit');


        Db::beginTransaction();
        try {

            Db::table('cms_tag_content')
                ->where('siteid', '=', $siteid)
                ->where('contentid', '=', $id)
                ->delete();
            $tags = isset($data['tag']) ? $data['tag'] : [];
            unset($data['tag']);

            $data = $this->filterNoExitField($modelinfo['tablename'], $data);
            Db::table($modelinfo['tablename'])
                ->where('siteid', '=', $siteid)
                ->where('id', '=', $id)
                ->update($data);

            //插入到标签内容表
            Db::table('cms_tag_content')
                ->where('contentid', '=', $id)
                ->where('cid', '=', $data['cid'])
                ->where('siteid', '=', $siteid)
                ->delete();
            $tagcontent = [];
            foreach ($tags as $tag) {
                $tagcontent[] = [
                    'cid'       => $data['cid'],
                    'modelid'   => $cateinfo['mid'],
                    'tagid'     => $tag,
                    'contentid' => $id,
                    'siteid'    => $siteid,
                ];
            }
            if ($tagcontent) {
                Db::table('cms_tag_content')
                    ->insert($tagcontent);
            }

            //启用钩子
            Event::dispatch('framecoder.hook', [
                'ident' => 'cms_content_edit', //钩子标识
                'data'  => $data
            ]);
            Db::commit();
        } catch (Throwable $ex) {
            Db::rollBack();
            throw $ex;
        }
    }

    /**
     * 移动分类
     *
     * @param array|string $contentids
     * @param integer $fromcid
     * @param integer $tocid
     * @param integer $siteid
     * @return void
     */
    public function moveCate(
        array|string $contentids,
        int $fromcid,
        int $tocid,
        int $siteid
    ) {

        Db::beginTransaction();
        try {
            $bllCategory = new Category();
            $formcate = $bllCategory->find($fromcid, $siteid);
            $tocate = $bllCategory->find($tocid, $siteid);
            if (!$formcate || !$tocate || $formcate['mid'] != $tocate['mid']) {
                throw new Exception('要转移的分类不正确');
            }

            $bllModel  = new Model();
            $modelinfo = $bllModel->find($tocate['mid'], $siteid);

            $contentids =  is_string($contentids) ? explode(',', $contentids) : $contentids;

            //修改内容表
            Db::table($modelinfo['tablename'])
                ->where('cid', '=', $fromcid)
                ->whereIn('id', $contentids)
                ->update([
                    'cid' => $tocid
                ]);

            //修改tag表
            Db::table('cms_tag_content')
                ->where('cid', '=', $fromcid)
                ->whereIn('contentid', $contentids)
                ->update([
                    'cid' => $tocid
                ]);

            //计算文章数
            $bllCategory->computeContentCountAtCid($fromcid, $siteid);
            $bllCategory->computeContentCountAtCid($tocid, $siteid);
            //启用钩子 内容移动分类
            Event::dispatch('framecoder.hook', [
                'ident' => 'cms_content_movecate',
                'data'  => [
                    'contentids' => $contentids,
                    'tocid'      => $tocid,
                    'siteid'     => $siteid,
                ]
            ]);



            Db::commit();
        } catch (Throwable $ex) {
            Db::rollBack();
            return $ex;
        }
    }



    /**
     * 内容加入到回收站 1 正常 -9 回收站
     *
     * @param string|array $contentids
     * @param integer $cid
     * @param integer $siteid
     * @return void
     */
    public function toRecycle(string|array $contentids, int $cid, int $siteid)
    {

        if (is_string($contentids)) {
            $contentids = explode(',', $contentids);
        }
        if (!$contentids) {
            throw new Exception('文章不存在');
        }


        $bllCategory = new Category();
        $tablename   = $bllCategory->getTableName($cid, $siteid);
        try {
            Db::table($tablename)
                ->whereIn('id', $contentids)
                ->where('siteid', '=', $siteid)
                ->update([
                    'status' => ContentStatus::STATUS_RECYCLE->value
                ]);
            $bllCategory->computeContentCountAtCid($cid, $siteid);
            //启用钩子
            Event::dispatch('framecoder.hook', [
                'ident' => 'cms_content_torecycle',
                'data'  => [
                    'contentids' => $contentids,
                    'cid'        => $cid,
                    'siteid'     => $siteid
                ]
            ]);
        } catch (Exception $ex) {
            throw $ex;
        }
    }

    /**
     * 彻底删除文章
     *
     * @param string|array $contentids
     * @param integer $cid
     * @param integer $siteid
     * @return void
     */
    public function delete(string|array $contentids, int $cid, int $siteid)
    {
        if (is_string($contentids)) {
            $contentids = explode(',', $contentids);
        }
        if (!$contentids) {
            throw new Exception('文章不存在');
        }
        Db::beginTransaction();
        try {

            $bllCategory = new Category();
            $tablename   = $bllCategory->getTableName($cid, $siteid);
            Db::table($tablename)
                ->whereIn('id', $contentids)
                ->where('siteid', '=', $siteid)
                ->delete();

            $bllTagContent = new TagContent();
            $bllTagContent->delContent($contentids, $cid, $siteid);

            //统计文章数
            $bllCategory->computeContentCountAtCid($cid, $siteid);
            //启用钩子
            Event::dispatch('framecoder.hook', [
                'ident' => 'cms_content_delete',
                'data'  => [
                    'contentids' => $contentids,
                    'cid'        => $cid,
                    'siteid'     => $siteid
                ]
            ]);

            Db::commit();
        } catch (Exception $ex) {
            Db::rollBack();
            throw $ex;
        }
    }

    /**
     * 恢复文章 从回收站中恢复
     *
     * @param string|array $contentids
     * @param integer $cid
     * @param integer $siteid
     * @return void
     */
    public function restore(string|array $contentids, int $cid, int $siteid)
    {
        if (is_string($contentids)) {
            $contentids = explode(',', $contentids);
        }
        if (!$contentids) {
            throw new Exception('文章不存在');
        }

        try {
            $bllCategory = new Category();
            $tablename   = $bllCategory->getTableName($cid, $siteid);
            Db::table($tablename)
                ->whereIn('id', $contentids)
                ->where('siteid', '=', $siteid)
                ->where('status', '=', ContentStatus::STATUS_RECYCLE->value)
                ->update([
                    'status' => ContentStatus::STATUS_NORMAL->value
                ]);
            //统计文章数
            $bllCategory->computeContentCountAtCid($cid, $siteid);
            //启用钩子
            Event::dispatch('framecoder.hook', [
                'ident' => 'cms_content_restore',
                'data'  => [
                    'contentids' => $contentids,
                    'cid'        => $cid,
                    'siteid'     => $siteid
                ]
            ]);
        } catch (Exception $ex) {
            throw $ex;
        }
    }


    /**
     * 浏览次数自增
     *
     * @return void
     */
    public function viewCountIncrement(int $id, int $cid, int $siteid)
    {
        $bllCategory = new Category();
        $tablename = $bllCategory->getTableName($cid, $siteid);
        Db::table($tablename)
            ->where('id', '=', $id)
            ->increment('viewscount', 1);
    }

    /**
     * 设置权重
     *
     * @param integer $id
     * @param integer $cid
     * @param integer $siteid
     * @param integer $weight
     * @return integer
     */
    public function setWeight(
        int $id,
        int $cid,
        int $siteid,
        int $weight
    ): int {
        $bllCategory = new Category();
        $tablename = $bllCategory->getTableName($cid, $siteid);
        return Db::table($tablename)
            ->where('id', '=', $id)
            ->update([
                'weight' => $weight
            ]);
    }

    /**
     * 格式化字段数据
     *
     * @param array $fields
     * @param array $data
     * @return array
     */
    private function formatFieldData(array $fields, array $data, string $type = 'add'): array
    {

        foreach ($fields as $key => $field) {
            if (isset($data[$field['name']]) && $field['issystem'] != 1) {
                if (is_array($data[$field['name']])) {
                    if ($field['sourcetype'] == 1) {
                        $data[$field['name']] = json_encode(array_values($data[$field['name']]), JSON_UNESCAPED_UNICODE);
                    } else {
                        $data[$field['name']] = json_encode($data[$field['name']], JSON_UNESCAPED_UNICODE);
                    }
                }
            }

            if (isset($data[$field['name']]) && $field['editor'] == 1) {
                $data[$field['name']] = htmlspecialchars_decode($data[$field['name']]);
            }

            if (!isset($data[$field['name']])) {
                if ($field['default'] != '') {
                    $data[$field['name']] = $field['default'];
                } else {
                    $data[$field['name']] = '';
                }
            }
        }

        if ($type == 'add') {
            $data['createtime'] = date('Y-m-d H:i:s');
        }
        $data['updatetime'] = date('Y-m-d H:i:s');


        return $data;
    }

    /**
     * 排除不存在的字段
     *
     * @param string $tablename
     * @param array $data
     * @return array
     */
    private function filterNoExitField(string $tablename, array $data): array
    {
        $table = new Table($tablename);
        $cloumns = $table->getColumns();
        $cloumns = array_column($cloumns, null, 'name');
        foreach ($data as $key => $d) {

            if (!isset($cloumns[$key])) {
                unset($data[$key]);
            }
        }
        return $data;
    }


    /**
     * 验证字段
     *
     * @param array $fields
     * @param array $data
     * @return void
     */
    private function verifyField(array $fields, array $data)
    {

        foreach ($fields as $field) {
            if (
                isset($data[$field['name']])
                && $field['require'] == 1
                && !trim($data[$field['name']])
            ) {
                throw new Exception($field['title'] . '必填');
            }

            if (
                isset($data[$field['name']]) &&
                trim(
                    $data[$field['name']]
                ) && $field['reg'] != ''
            ) {
                if (!preg_match($field['reg'], $data[$field['name']])) {
                    throw new Exception($field['title'] . '格式不正确');
                }
            }
        }
    }


    /**
     * 判断用户是有达到文章的阅读权限
     *
     * @param integer $uid
     * @param array $content
     * @return boolean
     */
    #[Template]
    public function userReachReadlimitation(
        int $uid,
        int $id,
        int $cid,
        int $siteid
    ): bool {
        $bllContent = new Content();
        $content = $bllContent->find(
            id: $id,
            cid: $cid,
            siteid: $siteid,
        );
        if ($content['readlimitation'] <= 0) {
            return true;
        }

        $bllUser = new User();
        $userinfo = $bllUser->find($uid);
        if (!$userinfo) {
            return false;
        }

        if ($content['readlimitation'] > 0) {
            $bllUserGrade = new UserGrade();
            $grade = $bllUserGrade->find($content['readlimitation']);
            if (!$grade) {
                return false;
            }

            if ($grade['points'] > $userinfo['integral']) {
                return false;
            }
        }

        return true;
    }

    /**
     * 判断用户是需要或购买过内容
     *
     * @param integer $uid
     * @param array $content
     * @return boolean
     */
    #[Template]
    public function userPointsExchange(
        int $uid,
        int $id,
        int $cid,
        int $siteid
    ): bool {

        $bllContent = new Content();
        $content = $bllContent->find(
            id: $id,
            cid: $cid,
            siteid: $siteid,
        );
        if ($content['points'] <= 0) {
            return true;
        }

        $bllUser = new User();
        $userinfo = $bllUser->find($uid);
        if (!$userinfo) {
            return false;
        }

        if ($content['points'] > 0) {

            //判断是否购买过记录
            $bllPointsExchange = new PointsExchange();
            return  $bllPointsExchange->isExchange(
                uid: $uid,
                cid: $content['cid'],
                contentid: $content['id']
            );
        }

        return true;
    }

    /**
     * 判断用户是否有阅读、购买文章权限
     *
     * @param integer $uid
     * @param array $content
     * @return boolean
     */
    #[Template]
    public function userCanViewContent(
        int $uid,
        int $id,
        int $cid,
        int $siteid
    ): bool {

        return $this->userReachReadlimitation($uid, $id, $cid, $siteid)
            && $this->userPointsExchange($uid,  $id, $cid, $siteid);
    }

    /**
     * 上一篇
     *
     * @param integer $id
     * @param integer $cid
     * @param integer $siteid
     * @param integer $status
     * @return array
     */
    #[Template]
    public function prevContent(int $id, int $cid, int $siteid, int $status = 1): array
    {
        $bllcategory = new Category();
        $cateinfo    = $bllcategory->find($cid, $siteid);

        $bllModel  = new Model();
        $modelinfo = $bllModel->find($cateinfo['mid'], $siteid);


        $thisinfo =  Db::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('id', '=', $id)
            ->where('status', '=', $status)
            ->first();

        if ($thisinfo === null) {
            return [];
        }
        $thisinfo = get_object_vars($thisinfo);

        $query1 = DB::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('weight', '>', $thisinfo['weight'])
            ->where('status', '=', $status)
            ->orderBy('weight')
            ->limit(1);


        $query2 = Db::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('weight', '=', $thisinfo['weight'])
            ->where('id', '>', $thisinfo['id'])
            ->where('status', '=', $status)
            ->orderByRaw('id ')
            ->limit(1);

        $combinedQuery = $query1->unionAll($query2);

        $content = $combinedQuery
            ->orderByRaw('weight desc , id desc')
            ->limit(1)
            ->first();

        return $content !== null ? get_object_vars($content) : [];
    }

    /**
     * 下一篇
     *
     * @param integer $id
     * @param integer $cid
     * @param integer $siteid
     * @return array
     */
    #[Template]
    public function nextContent(int $id, int $cid, int $siteid, int $status = 1): array
    {
        $bllcategory = new Category();
        $cateinfo    = $bllcategory->find($cid, $siteid);

        $bllModel  = new Model();
        $modelinfo = $bllModel->find($cateinfo['mid'], $siteid);


        $thisinfo =  Db::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('id', '=', $id)
            ->where('status', '=', $status)
            ->first();

        if ($thisinfo === null) {
            return [];
        }

        $thisinfo = get_object_vars($thisinfo);

        $query1 = DB::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('status', '=', $status)
            ->where('weight', '<', $thisinfo['weight'])
            ->orderByDesc('weight')
            ->limit(1);


        $query2 = Db::table($modelinfo['tablename'])
            ->where('cid', '=', $cid)
            ->where('status', '=', $status)
            ->where('weight', '=', $thisinfo['weight'])
            ->where('id', '<', $thisinfo['id'])
            ->orderByRaw('id desc')
            ->limit(1);

        $combinedQuery = $query1->unionAll($query2);

        $content = $combinedQuery
            ->orderByRaw('weight desc , id desc')
            ->limit(1)
            ->first();

        return $content !== null ? get_object_vars($content) : [];
    }

    /**
     * 文章总数
     *
     * @param string $modelTable 模型表名
     * @param integer $cid
     * @param integer $status
     * @param integer $siteid
     * @return integer
     */
    public function contentCount(string $modelTable, ?int $cid = null, ?int $status = null, ?int $uid = null, ?int $siteid = null): int
    {
        return Db::table($modelTable)
            ->when($siteid !== null, function ($query) use ($siteid) {
                return $query->where('siteid', '=', $siteid);
            })
            ->when($cid !== null, function ($query) use ($cid) {
                return $query->where('cid', '=', $cid);
            })
            ->when($status !== null, function ($query) use ($status) {
                return  $query->where('status', '=', $status);
            })
            ->when($uid !== null, function ($query) use ($uid) {
                return  $query->where('uid', '=', $uid);
            })
            ->count();
    }


    /**
     * 分页查询
     *
     * @param integer $pagesize
     * @return LengthAwarePaginator
     */
    #[Template]
    public function paginate(
        int|array $cid = [],
        ?int $siteid = null,
        ?int $mid = null,
        ?int $uid = null,
        ?int $contribute = null,
        int $status = ContentStatus::STATUS_NORMAL->value,
        string $order = 'weight desc, id desc',
        int $pagesize = 20,
        ...$searchparam
    ): LengthAwarePaginator {

        if (!$cid && !$mid) {
            throw new Exception('分类或模型不能全部为空');
        }
        $modelinfo = [];
        if ($mid) {
            $bllModel  = new Model();
            $modelinfo = $bllModel->find($mid);
        }

        $bllcategory = new Category();
        $allcids = [];
        if (is_numeric($cid)) {

            $cateinfo    = $bllcategory->find($cid);
            $descendants = $bllcategory->getDescendantsAndSelf($cid, $siteid);
            $allcids     = array_column($descendants, 'id');
        }
        if (is_array($cid) && $cid) {
            $allcids = $cid;
            $cateinfo    = $bllcategory->find($cid[0]);
        }

        if (!$modelinfo) {
            $bllModel  = new Model();
            $modelinfo = $bllModel->find($cateinfo['mid'], $siteid);
        }


        //获取搜索字段
        $bllModelField = new ModelField();
        $searchields = $bllModelField->selSearchField($modelinfo['id']);

        $queryObj = Db::table($modelinfo['tablename'])
            ->when($siteid != null, function ($query) use ($siteid) {
                $query->where('siteid', '=', $siteid);
            })
            ->when($allcids, function ($query) use ($allcids) {
                $query->whereIn('cid', $allcids);
            })
            ->when($uid != null, function ($query) use ($uid) {
                $query->where('uid', '=', $uid);
            })
            ->when($uid != null, function ($query) use ($uid) {
                $query->where('uid', '=', $uid);
            })
            ->when($contribute != null, function ($query) use ($contribute) {
                $query->where('contribute', '=', $contribute);
            })
            ->where('status', '=', $status);

        if ($searchields) {
            $searchields = array_column($searchields, null, 'name');
            foreach ($searchparam as $key => $param) {
                if (!key_exists($key, $searchields)) {
                    continue;
                }
                if (trim($param) == '') {
                    continue;
                }

                switch ($searchields[$key]['search']) {
                    case '1':
                        $queryObj =  $queryObj->where($searchields[$key]['name'], '=',  $param);
                        break;
                    case '2':
                        $queryObj = $queryObj->where($searchields[$key]['name'], 'like', '%' . $param . '%');
                        break;
                    case '3':
                        $queryObj->where($searchields[$key]['name'], '<>', '');
                        $queryObj = $queryObj->whereRaw("json_contains( `" . $searchields[$key]['name'] . "`, ?)", '"' . $param . '"');
                        break;
                }
            }
        }


        return $queryObj
            ->orderByRaw($order)
            ->paginate($pagesize);
    }
}
